PedigreeImport = function () {
};

PedigreeImport.prototype = {
};

PedigreeImport.initFromPhenotipsInternal = function(inputG)
{

    var newG = new BaseGraph();

    var nameToId = {};

    var relationshipHasExplicitChHub = {};

    for (var v = 0; v < inputG.length; v++) {

        if (!inputG[v].hasOwnProperty("name") && !inputG[v].hasOwnProperty("id"))
            throw "Invalid inpiut: a node without id and without name";

        var type = TYPE.PERSON;
        if ( inputG[v].hasOwnProperty('relationship') || inputG[v].hasOwnProperty('rel') ) {
            type = TYPE.RELATIONSHIP;
            if (inputG[v].hasOwnProperty('hub') || inputG[v].hasOwnProperty('haschhub'))
                relationshipHasExplicitChHub[v] = true;
        }
        else if ( inputG[v].hasOwnProperty('chhub') ) {
            type = TYPE.CHILDHUB;
        }
        else if ( inputG[v].hasOwnProperty('virtual') || inputG[v].hasOwnProperty('virt')) {
            type = TYPE.VIRTUALEDGE;
        }

        var properties = {};
        if (inputG[v].hasOwnProperty('properties') || inputG[v].hasOwnProperty('prop'))
            properties = inputG[v].hasOwnProperty('properties') ? inputG[v]["properties"] : inputG[v]["prop"];

        if ( type == TYPE.PERSON ) {
            if (properties.hasOwnProperty("sex") && !properties.hasOwnProperty("gender")) {
                properties["gender"] = properties["sex"];
            }
                    
            if (!properties.hasOwnProperty("gender"))
                properties["gender"] = "U";

            if (inputG[v].hasOwnProperty("gender")) {
                 var genderString = inputG[v]["gender"].toLowerCase();
                 if( genderString == "female" || genderString == "f")
                    properties["gender"] = "F";
                else if( genderString == "male" || genderString == "m")
                    properties["gender"] = "M";
            }
        }

        var width = inputG[v].hasOwnProperty('width') ?
                    inputG[v].width :
                    (type == TYPE.PERSON ? newG.defaultPersonNodeWidth : newG.defaultNonPersonNodeWidth);

        var newID = newG._addVertex( null, type, properties, width );   

        if (inputG[v].hasOwnProperty("name")) {  
            if (nameToId[inputG[v].name])
                throw "Invalid user input: multiple nodes with the same name";
            if (type == TYPE.PERSON)
                newG.properties[newID]["fName"] = inputG[v].name;
            nameToId[inputG[v].name] = newID;
        }
        if ( type == TYPE.RELATIONSHIP && !relationshipHasExplicitChHub.hasOwnProperty(v) ) {
            var chHubId = newG._addVertex(null, TYPE.CHILDHUB, null, width );
            nameToId["_chhub_" + newID] = chHubId;
        }
    }

    for (var v = 0; v < inputG.length; v++) {
        var nextV = inputG[v];

        var vID    = nextV.hasOwnProperty("id") ? nextV.id : nameToId[nextV.name];
        var origID = vID;

        var substitutedID = false;

        if (newG.type[vID] == TYPE.RELATIONSHIP && !relationshipHasExplicitChHub.hasOwnProperty(vID)) {
            var childhubID = nameToId["_chhub_" + vID];
            vID = childhubID;
            substitutedID = true;
        }

        var maxChildEdgeWeight = 0;

        if (nextV.outedges) {
            for (var outE = 0; outE < nextV.outedges.length; outE++) {
                var target   = nextV.outedges[outE].to;
                var targetID = nameToId[target] ? nameToId[target] : target;  

                if (!newG.isValidId(targetID))
                    throw "Invalid input: invalid edge target (" + target + ")";

                var weight = 1;
                if (nextV.outedges[outE].hasOwnProperty('weight'))
                    weight = nextV.outedges[outE].weight;
                if ( weight > maxChildEdgeWeight )
                    maxChildEdgeWeight = weight;

                newG.addEdge( vID, targetID, weight );
            }
        }

        if (substitutedID) {
            newG.addEdge( origID, vID, maxChildEdgeWeight );
        }
    }

    newG.validate();

    return newG;
}

PedigreeImport.initFromPED = function(inputText, acceptOtherPhenotypes, markEvaluated, saveIDAsExternalID, affectedCodeOne, disorderNames)
{
    var inputLines = inputText.match(/[^\r\n]+/g);
    if (inputLines.length == 0) throw "Unable to import: no data";
            
    var postMakeped = false;
    if (inputLines[0].indexOf("Ped:") > 0 && inputLines[0].indexOf("Per:") > 0)
        postMakeped = true;

    var familyPrefix = "";

    var newG = new BaseGraph();

    var nameToId = {};

    var phenotypeValues = {}; 

    var extendedPhenotypesFound = false;

    var nextID = postMakeped ? 1 : 0;

    for (var i = 0; i < inputLines.length; i++) {

        inputLines[i] = inputLines[i].replace(/[^a-zA-Z0-9_.\-\s*]/g, ' ');
        inputLines[i] = inputLines[i].replace(/^\s+|\s+$/g, '');  // trim()

        var parts = inputLines[i].split(/\s+/);

        if (parts.length < 6 || (postMakeped && parts.length < 10)) {
            throw "Input line has not enough columns: [" + inputLines[i] + "]";
        }

        if (familyPrefix == "") {
            familyPrefix = parts[0];
        } else {
            if (parts[0] != familyPrefix) {
                throw "Unsupported feature: multiple families detected within the same pedigree";
            }
        }

        var pedID = parts[1];
        if (nameToId.hasOwnProperty(pedID))
            throw "Multiple persons with the same ID [" + pedID + "]";

        var genderValue = postMakeped ? parts[7] : parts[4];
        var gender = "U";
        if (genderValue == 1)
            gender = "M";
        else if (genderValue == 2)
            gender = "F";
        var properties = {"gender": gender};

        if (saveIDAsExternalID)
            properties["externalID"] = pedID;

        var useID = (postMakeped && parts[8] == 1) ? 0 : nextID++;
        if (i == inputLines.length-1 && newG.v[0] === undefined) {
            // last node and no node with id 0 yet
            useID = 0;
        }

        var pedigreeID = newG._addVertex( useID, TYPE.PERSON, properties, newG.defaultPersonNodeWidth );

        nameToId[pedID] = pedigreeID;

        var phenotype = postMakeped ? parts[9] : parts[5];
        phenotypeValues[phenotype] = true;
        if (acceptOtherPhenotypes && phenotype != "-9" && phenotype != "0" && phenotype != "1" && phenotype != "2") {
            extendedPhenotypesFound = true;
        }
    }

    if (affectedCodeOne) {
        if (extendedPhenotypesFound || phenotypeValues.hasOwnProperty("2")) {
            throw "Phenotypes with codes other than 0 or 1 were found";
        }
        var affectedValues   = { "1":  true };
        var missingValues    = { "-9": true };
        var unaffectedValues = { "0":  true };
    } else {
        var affectedValues   = { "2": true };
        var missingValues    = { "0": true, "-9": true };
        var unaffectedValues = { "1": true };
    }

    if (!disorderNames) {
        disorderNames = {};
        if (extendedPhenotypesFound) {
            for (var phenotype in phenotypeValues)
                if (phenotypeValues.hasOwnProperty(phenotype)) {
                    if (phenotype != "-9" && phenotype != "0" && phenotype != "1") {
                        disorderNames[phenotype]  = "affected (phenotype " + phenotype + ")";
                        affectedValues[phenotype] = true;
                    }
                }
        }
    }

    var defaultEdgeWeight = 1;

    var relationshipTracker = new RelationshipTracker(newG, defaultEdgeWeight);

    for (var i = 0; i < inputLines.length; i++) {
        var parts = inputLines[i].split(/\s+/);

        var thisPersonName = parts[1];
        var id = nameToId[thisPersonName];

        var phenotype = postMakeped ? parts[9] : parts[5];
        if (affectedValues.hasOwnProperty(phenotype)) {
            var disorder = disorderNames.hasOwnProperty(phenotype) ? disorderNames[phenotype] : "affected";
            newG.properties[id]["carrierStatus"] = 'affected';
            newG.properties[id]["disorders"]     = [disorder];
            if (markEvaluated)
                newG.properties[id]["evaluated"] = true;
        } else if (unaffectedValues.hasOwnProperty(phenotype)) {
            newG.properties[id]["carrierStatus"] = '';
            if (markEvaluated)
                newG.properties[id]["evaluated"] = true;
        } else if (!missingValues.hasOwnProperty(phenotype)) {
        }


        var fatherID = parts[2];
        var motherID = parts[3];

        if (fatherID == 0 && motherID == 0) continue;

        if (fatherID == 0) {
           fatherID = newG._addVertex( null, TYPE.PERSON, {"gender": "M", "comments": "unknown"}, newG.defaultPersonNodeWidth );
        } else {
            fatherID = nameToId[fatherID];
            if (newG.properties[fatherID].gender == "F")
                throw "Unable to import pedigree: a person declared as female [id: " + fatherID + "] is also declared as being a father for [id: "+thisPersonName+"]";
        }
        if (motherID == 0) {
            motherID = newG._addVertex( null, TYPE.PERSON, {"gender": "F", "comments": "unknown"}, newG.defaultPersonNodeWidth );
        } else {
            motherID = nameToId[motherID];
            if (newG.properties[motherID].gender == "M")
                throw "Unable to import pedigree: a person declared as male [id: " + motherID + "] is also declared as being a mother for [id: "+thisPersonName+"]";
        }

        var chhubID = relationshipTracker.createOrGetChildhub(motherID, fatherID);
        
        newG.addEdge( chhubID, id, defaultEdgeWeight );
    }

    PedigreeImport.validateBaseGraph(newG);
    
    return newG;
}

PedigreeImport.initFromBOADICEA = function(inputText, saveIDAsExternalID)
{
    var inputLines = inputText.match(/[^\r\n]+/g);

    if (inputLines.length <= 2) {
        throw "Unable to import: no data";
    }
    if (inputLines[0].match(/^BOADICEA import pedigree file format 2/i) === null) {
        throw "Unable to import: unsupported version of the BOADICEA format";
    }
    inputLines.splice(0,2); 

    var familyPrefix = "";

    var newG = new BaseGraph();

    var nameToId = {};

    var nextID = 1;

    for (var i = 0; i < inputLines.length; i++) {

        inputLines[i] = inputLines[i].replace(/[^a-zA-Z0-9_.\-\s*]/g, ' ');
        inputLines[i] = inputLines[i].replace(/^\s+|\s+$/g, '');  // trim()

        var parts = inputLines[i].split(/\s+/);

        if (parts.length < 24) {
            throw "Input line has not enough columns: [" + inputLines[i] + "]";
        }

        if (familyPrefix == "") {
            familyPrefix = parts[0];
        } else {
            if (parts[0] != familyPrefix) {
                throw "Unsupported feature: multiple families detected within the same pedigree";
            }
        }

        var extID = parts[3];
        if (nameToId.hasOwnProperty(extID))
            throw "Multiple persons with the same ID [" + extID + "]";

        var genderValue = parts[6];
        var gender = "M";
        if (genderValue == "F") {
          gender = "F";
        }
        var name = parts[1];
        if (isInt(name)) {
          name = "";
        }
        var properties = {"gender": gender, "fName": name};

        if (saveIDAsExternalID) {
          properties["externalID"] = extID;
        }

        var deadStatus = parts[8];
        if (deadStatus == "1") {
          properties["lifeStatus"] = "deceased";
        }

        var yob = parts[10];
        if (yob != "0") {
          var dob = yob + "-01-01T00:00:00.000Z";
          properties["dob"] = dob;
        }

        var cancers = [ { "column": 11, "label": "Breast cancer",           "disorder": "1BrCa"},
                        { "column": 12, "label": "Contralateral breast c.", "disorder": "2BrCa"},
                        { "column": 13, "label": "Ovarian cancer",          "disorder": "OvCa"},
                        { "column": 14, "label": "Prostate cancer",         "disorder": "ProCa"},
                        { "column": 15, "label": "Pancreatic cancer",       "disorder": "PanCa"} ];

        for (var c = 0; c < cancers.length; c++) {
          var cancer = cancers[c];
          if (parts[cancer["column"]].toUpperCase() != "AU") {
            if (!properties.hasOwnProperty("comments")) {
              properties["comments"] = "";
            } else {
              properties["comments"] += "\n";
            }

            if (parts[cancer["column"]] == "0") {
              properties["comments"] += "[-] " + cancer["label"] + ": unaffected";
            } else {
              properties["comments"] += "[+] " + cancer["label"] + ": at age " + parts[cancer["column"]];
              if (!properties.hasOwnProperty("disorders")) {
                properties["disorders"] = [];
              }
              properties["disorders"].push(cancer["disorder"]);
            }
          }
        }

        var ashkenazi = parts[18];
        if (ashkenazi != "0") {
          properties["ethnicities"] = ["Ashkenazi Jews"];
        }

        var proband = (parts[2] == 1);
        var useID = proband ? 0 : nextID++;
        if (i == inputLines.length-1 && newG.v[0] === undefined) {
          useID = 0;
        }

        var pedigreeID = newG._addVertex( useID, TYPE.PERSON, properties, newG.defaultPersonNodeWidth );

        nameToId[extID] = pedigreeID;
    }

    var defaultEdgeWeight = 1;

    var relationshipTracker = new RelationshipTracker(newG, defaultEdgeWeight);

    for (var i = 0; i < inputLines.length; i++) {
      var parts = inputLines[i].split(/\s+/);

      var extID = parts[3];
      var id    = nameToId[extID];
      var fatherID = parts[4];
      var motherID = parts[5];

      if (fatherID == 0 && motherID == 0) {
        continue;
      }

      if (fatherID == 0) {
       fatherID = newG._addVertex( null, TYPE.PERSON, {"gender": "M", "comments": "unknown"}, newG.defaultPersonNodeWidth );
      } else {
        fatherID = nameToId[fatherID];
        if (newG.properties[fatherID].gender == "F") {
          throw "Unable to import pedigree: a person declared as female [id: " + fatherID + "] is also declared as being a father for [id: "+extID+"]";
        }
      }
      if (motherID == 0) {
        motherID = newG._addVertex( null, TYPE.PERSON, {"gender": "F", "comments": "unknown"}, newG.defaultPersonNodeWidth );
      } else {
        motherID = nameToId[motherID];
        if (newG.properties[motherID].gender == "M") {
          throw "Unable to import pedigree: a person declared as male [id: " + motherID + "] is also declared as being a mother for [id: "+extID+"]";
        }
      }

      var chhubID = relationshipTracker.createOrGetChildhub(motherID, fatherID);

      newG.addEdge( chhubID, id, defaultEdgeWeight );
    }

    PedigreeImport.validateBaseGraph(newG);

    return newG;
}

PedigreeImport.validateBaseGraph = function(newG)
{
    try {
        newG.validate();
    } catch( err) {
        if (err.indexOf("disconnected component")) {
            throw "Unsupported pedigree: some components of the imported pedigree are disconnected from each other";
        } else {
            throw "Unable to import pedigree";
        }
    }
}

PedigreeImport.initFromSimpleJSON = function(inputText)
{
   try {
       var inputArray = JSON.parse(inputText);
   } catch( err) {
       throw "导入JSON数据格式错误！ " + err;
   }

   if (typeof inputArray != 'object' || Object.prototype.toString.call(inputArray) !== '[object Array]') {
       throw "导入JSON数据格式错误！";
   }
   if (inputArray.length == 0) {
       throw "不能导入空数据";
   }

   var newG = new BaseGraph();

   var nameToID            = {};
   var externalIDToID      = {};
   var ambiguousReferences = {};
   var hasID               = {}

   for (var i = 0; i < inputArray.length; i++) {
      if (inputArray[i].hasOwnProperty("relationshipId")) {
               continue;
           }
       var nextPerson = inputArray[i];

       if (typeof nextPerson != 'object') {
           throw "导入JSON数据格式错误！";
       }

       if ( !nextPerson.hasOwnProperty("id") && !nextPerson.hasOwnProperty("name") &&
            !nextPerson.hasOwnProperty("firstName") && !nextPerson.hasOwnProperty("externalId") ) {
           throw "导入错误: 节点ID或姓名已经存在";
       }

       var pedigreeID = newG._addVertex( null, TYPE.PERSON, {}, newG.defaultPersonNodeWidth );

       var properties = {};
       properties["gender"] = "U";     // 必填
       for (var property in nextPerson) {
           if (nextPerson.hasOwnProperty(property)) {
               var value    = nextPerson[property];
               var property = property.toLowerCase();
               
               if (property == "mother" || property == "father")  
                   continue;
               
               if (property == "sex") {
                   var genderString = value.toLowerCase();
                   if( genderString == "female" || genderString == "f")
                       properties["gender"] = "F";
                   else if( genderString == "male" || genderString == "m")
                       properties["gender"] = "M";
               } else if (property == "id") {
                   if (externalIDToID.hasOwnProperty(value)) {
                       throw "导入错误: 多个人的ID相同了 [" + value + "]"; 
                   }
                   if (nameToID.hasOwnProperty(value) && nameToID[value] != pedigreeID) {
                       delete nameToID[value];
                       ambiguousReferences[value] = true;
                   } else {
                       externalIDToID[value] = pedigreeID;
                       hasID[pedigreeID] = true;
                   }
                    properties["id"] = value;
               } else if (property == "name" || property == "firstname" ) {
                   properties["fName"] = value;
                   if (nameToID.hasOwnProperty(value) && nameToID[value] != pedigreeID) {
                       delete nameToID[value];
                       ambiguousReferences[value] = true;
                   } else if (externalIDToID.hasOwnProperty(value) && externalIDToID[value] != pedigreeID) {
                       delete externalIDToID[value];
                       ambiguousReferences[value] = true;
                   } else {
                       nameToID[value] = pedigreeID;
                   }
               } else if(property=="memberID"){
                   properties["memberID"] = value;
               }else if(property=="curentage"){
                    properties["curentAge"] = value;
               }else if(property=="deathage"){
                    properties["deathAge"] = value;
               }else if(property=="familyuserid"){
                    properties["familyUserId"] = value;
               }else {
                   var processed = PedigreeImport.convertProperty(property, value);
                   if (processed !== null) {
                       properties[processed.propertyName] = processed.value;
                   }
               }
           }
       }

       if (nextPerson.hasOwnProperty("externalId") && !hasID.hasOwnProperty(pedigreeID)) {
           externalIDToID[nextPerson.externalId] = pedigreeID;
           hasID[pedigreeID] = true;
       }
       newG.properties[pedigreeID] = properties;
   }

   var getPersonID = function(person) {
       if (person.hasOwnProperty("id"))
           return externalIDToID[person.id];

       if (person.hasOwnProperty("firstName"))
           return nameToID[person.firstName];

       if (person.hasOwnProperty("name"))
           return nameToID[person.name];
   };

   var findReferencedPerson = function(reference, refType) {
       if (ambiguousReferences.hasOwnProperty(reference))
           throw "导入错误: ambiguous reference to [" + reference + "]";
       
       if (externalIDToID.hasOwnProperty(reference))
           return externalIDToID[reference];
       
       if (nameToID.hasOwnProperty(reference))
           return nameToID[reference];
       
       throw "导入错误: [" + reference + "] is not a valid " + refType + " reference (does not correspond to a name or an ID of another person)";
   };

   var defaultEdgeWeight = 1;

   var relationshipTracker = new RelationshipTracker(newG, defaultEdgeWeight);

   for (var i = 0; i < inputArray.length; i++) {
        if (inputArray[i].hasOwnProperty("relationshipId")) {
               continue;
        }
       var nextPerson = inputArray[i];
       
       var personID = getPersonID(nextPerson);
       
       var motherLink = nextPerson.hasOwnProperty("mother") ? nextPerson["mother"] : null;
       var fatherLink = nextPerson.hasOwnProperty("father") ? nextPerson["father"] : null;
       
       if (motherLink == null && fatherLink == null)
           continue;
           
       if (fatherLink == null) {
           var fatherID = newG._addVertex( null, TYPE.PERSON, {"gender": "M", "comments": "unknown"}, newG.defaultPersonNodeWidth );
       } else {
           var fatherID = findReferencedPerson(fatherLink, "father");
           if (newG.properties[fatherID].gender == "F")
               throw "数据有误("+fatherLink+")";
       }
       if (motherLink == null) {
           var motherID = newG._addVertex( null, TYPE.PERSON, {"gender": "F", "comments": "unknown"}, newG.defaultPersonNodeWidth );
       } else {
           var motherID = findReferencedPerson(motherLink, "mother");
           if (newG.properties[motherID].gender == "M")
               throw "数据有误("+motherLink+")";          
       }
       
       if (fatherID == personID || motherID == personID)
           throw "数据有误";

       
       var chhubID = relationshipTracker.createOrGetChildhub(motherID, fatherID);

       newG.addEdge( chhubID, personID, defaultEdgeWeight );
   }
   for (var i = 0; i < inputArray.length; i++) {
           if (!inputArray[i].hasOwnProperty("relationshipId")) {
               continue;
           }
           var nextRelationship = inputArray[i];
           var partnerLink1 = nextRelationship.hasOwnProperty("partner1") ? nextRelationship["partner1"] : null;
           var partnerLink2 = nextRelationship.hasOwnProperty("partner2") ? nextRelationship["partner2"] : null;
           if (partnerLink1 == null || partnerLink2 == null) {
               throw "数据有误";
           }
           var partnerID1 = findReferencedPerson(partnerLink1);
           var partnerID2 = findReferencedPerson(partnerLink2);
           var chhubID = relationshipTracker.createOrGetChildhub(partnerID1, partnerID2);
           if (newG.getOutEdges(chhubID).length == 0) {
               var placeholderID = newG._addVertex(null, BaseGraph.TYPE.PERSON, {"gender": "U", "placeholder":true}, newG.defaultPersonNodeWidth );
               newG.addEdge( chhubID, placeholderID, defaultEdgeWeight );
           }

           var relationshipID = relationshipTracker.getRelationshipIDForChildHub(chhubID);
           for (var property in nextRelationship) {
               if (nextRelationship.hasOwnProperty(property)) {
                   var value    = nextRelationship[property];
                   var property = property.toLowerCase();

                   if (property == "partner1" || property == "partner2")  
                       continue;
                   var processed = PedigreeImport.convertRelationshipProperty(property, value);
                   if (processed !== null) {
                       newG.properties[relationshipID][processed.propertyName] = processed.value;
                   }
               }
           }
   }
   PedigreeImport.validateBaseGraph(newG);

   return newG;
}


PedigreeImport.initFromGEDCOM = function(inputText, markEvaluated, saveIDAsExternalID)
{  
   var inputLines = inputText.match(/[^\r\n]+/g);
   if (inputLines.length == 0) throw "Unable to import: no data";

   var convertToObject = function(inputLines) {
       var obj = { "header": {}, "individuals": [], "families": [] };
       
       var currentObject = [];
       
       for (var i = 0; i < inputLines.length; i++) {           
           var nextLine = inputLines[i].replace(/[^a-zA-Z0-9.\@\/\-\s*]/g, ' ').replace(/^\s+|\s+$/g, ''); // sanitize + trim          
           
           var words = inputLines[i].split(/\s+/);           
           var parts = words.splice(0,2);
           parts.push(words.join(' '));
           
           
           var level = parseInt(parts[0]);
           
           currentObject.splice(level);
           
           if (level == 0) {
               if (parts[1] == "HEAD") {
                   currentObject[0] = obj.header;
               } else if (parts[1][0] == '@' && parts[2] == "INDI") {
                   obj.individuals.push({});
                   currentObject[0] = obj.individuals[obj.individuals.length - 1];
                   currentObject[0]["id"] = parts[1]; 
               } else if (parts[1][0] == '@' && parts[2] == "FAM") {
                   obj.families.push({});
                   currentObject[0] = obj.families[obj.families.length - 1];
                   currentObject[0]["id"] = parts[1];
               } else {
                   currentObject[0] = {};
               }
           } else {
               if (currentObject.length < level - 1) {
                   throw "Unable to import GEDCOM: a multi-level jump detected in line: [" + inputLines[i] + "]";
               }

               if (!currentObject[level-1].hasOwnProperty(parts[1]))
                   currentObject[level-1][parts[1]] = [];  // array of values
               
               if (currentObject.length < level + 1) {
                   currentObject[level] = {};
                   currentObject[level - 1][parts[1]].push(currentObject[level]);
               }
               
               if (parts[2] != "") {
                   currentObject[level]["value"] = parts[2];                   
               }
           }           
           currentLevel = parts[0];
       }
       
       return obj;
   };
   
   var gedcom = convertToObject(inputLines);   
   console.log("GEDCOM object: " + stringifyObject(gedcom));
   
   if (gedcom.header.hasOwnProperty("GEDC")) {
       if (gedcom.header.GEDC.hasOwnProperty("VERS")) {
           if (gedcom.header.GEDC.VERS != "5.5" && gedcom.header.GEDC.VERS != "5.5.1") {
               alert("Unsupported GEDCOM version detected: [" + gedcom.header.GEDC.VERS + "]. "+
                     "Import will continue but the correctness is not guaranteed. Supportede versions are 5.5 and 5.5.1");   
           }
       }
   }
   
   if (gedcom.individuals.length == 0) {
       throw "Unable to create a pedigree from GEDCOM: no individuals are defined in the import data";
   }
   
   var newG = new BaseGraph();
   
   var externalIDToID = {};

   for (var i = 0; i < gedcom.individuals.length; i++) {
       var nextPerson =  gedcom.individuals[i];

       var pedigreeID = newG._addVertex( null, TYPE.PERSON, {}, newG.defaultPersonNodeWidth );
       
       externalIDToID[nextPerson.id] = pedigreeID; 
                      
       var cleanedID = nextPerson.id.replace(/@/g, '')
       var properties = saveIDAsExternalID ? {"externalID": cleanedID} : {};

       properties["gender"] = "U";  

       var getFirstValue = function(obj) {
           return obj[0].value;
       }
       
       var parseDate = function(gedcomDate) {
           gedcomDate = gedcomDate[0].value;
           
           gedcomDate = gedcomDate.replace(/^(\s*)ABT(\s*)/,"");
           gedcomDate = gedcomDate.replace(/^(\s*)EST(\s*)/,"");
           gedcomDate = gedcomDate.replace(/^(\s*)BEF(\s*)/,"");
           gedcomDate = gedcomDate.replace(/^(\s*)AFT(\s*)/,"");
           var getBetweenDate = /^\s*BET\s+(.+)\s+AND.*/;
           var match = getBetweenDate.exec(gedcomDate);
           if (match != null) {
               gedcomDate = match[1];
           }

           if (gedcomDate == "?") return null;
           
           var timestamp=Date.parse(gedcomDate)
           if (isNaN(timestamp)==false) {
               return new Date(timestamp);
           }           
           return null;
       };
       
       for (var property in nextPerson) {
           if (nextPerson.hasOwnProperty(property)) {
               if (property == "SEX") {
                   var genderString = getFirstValue(nextPerson[property])[0].toLowerCase(); // use first character only
                   if( genderString == "female" || genderString == "f")
                       properties["gender"] = "F";
                   else if( genderString == "male" || genderString == "m")
                       properties["gender"] = "M";
               } else if (property == "BIRT") {
                   if (nextPerson[property][0].hasOwnProperty("DATE")) {
                       var date = parseDate(nextPerson[property][0]["DATE"]);
                       if (date !== null) {
                           properties["dob"] = date;
                       }
                   }
               } else if (property == "DEAT") {
                   if (properties.hasOwnProperty("lifeStatus") && properties["lifeStatus"] == "stillborn")
                       continue;
                   properties["lifeStatus"] = "deceased";
                   if (nextPerson[property][0].hasOwnProperty("DATE")) {
                       var date = parseDate(nextPerson[property][0]["DATE"]);
                       if (date !== null) {
                           properties["dod"] = date;
                       }
                   }
               } else if (property == "ADOP") {
                   properties["isAdopted"] = true;
               } else if (property == "_INFO") {
                   if (!properties.hasOwnProperty("comments"))
                       properties["comments"] = "";
                   properties["comments"] += "(Info: " + getFirstValue(nextPerson[property]) + ")\n";
               } else if (property == "NOTE" || property == "_COMMENT") {
                   if (!properties.hasOwnProperty("comments"))
                       properties["comments"] = "";
                   properties["comments"] += getFirstValue(nextPerson[property]) + "\n";
                   if (nextPerson[property][0].hasOwnProperty("CONT")) {
                       var more = nextPerson[property][0]["CONT"];
                       for (var cc = 0; cc < more.length; cc++) {
                           properties["comments"] += more[cc].value + "\n";
                       }
                   }
               } else if (property == "NAME") {
                   var nameParts = getFirstValue(nextPerson[property]).split('/');
                   var firstName = nameParts[0].replace(/^\s+|\s+$/g, '');
                   var lastName  = nameParts.length > 1 ? nameParts[1].replace(/^\s+|\s+$/g, '') : "";
                   properties["fName"] = firstName;
                   if (lastName != "")
                       properties["lName"] = lastName;
               } else if (property == "_MAIDEN") {
                   var nameParts = getFirstValue(nextPerson[property]).split('/');
                   var firstName = nameParts[0].replace(/^\s+|\s+$/g, '');
                   var lastName  = nameParts.length > 1 ? nameParts[1].replace(/^\s+|\s+$/g, '') : "";
                   properties["lNameAtB"] = firstName;
                   if (lastName != "")
                       properties["lNameAtB"] += " " + lastName;               
               } else if (property == "_GENSTAT") {
                   var props = getFirstValue(nextPerson[property]).split('');
                   for (var p = 0; p < props.length; p++) {
                       var value = props[p];
                       if (value.charCodeAt(0) == 65533 || value.charCodeAt(0) == 172) {
                           // one value is obtained via copy-paste, another via file upload
                           value = "HEARSAY";
                       }
                       switch(value) {
                       case "O":
                           properties["carrierStatus"] = 'affected';
                           properties["disorders"]     = ['affected'];
                           if (markEvaluated)
                               properties["evaluated"] = true;
                           break;
                       case "HEARSAY":
                           properties["carrierStatus"] = 'presymptomatic';
                           if (markEvaluated)
                               properties["evaluated"] = true;
                           break;
                       case "K":
                           properties["lifeStatus"] = "stillborn";
                           break;
                       case "M":
                           properties["childlessStatus"] = "infertile";
                           break;
                       case "E":
                           if (!properties.hasOwnProperty("comments")) {
                               properties["comments"] = "(untested)";
                           }
                           else { 
                               properties["comments"] = "(untested)\n" + properties["comments"];
                           }
                           break;
                       case "O":
                           // TODO: proband
                           break;
                       } 
                   }
               }
           }
       }
       if (properties.hasOwnProperty("comments")) {
           properties.comments = properties.comments.replace(/^\s+|\s+$/g, '');
           if (properties.comments == "")
               delete properties.comments;
       }
       newG.properties[pedigreeID] = properties;
   }

   var defaultEdgeWeight = 1;
   
   var relationshipTracker = new RelationshipTracker(newG, defaultEdgeWeight);
   
   var noChildFamilies = [];

   for (var i = 0; i < gedcom.families.length; i++) {
       var nextFamily = gedcom.families[i];
      
       var motherLink = nextFamily.hasOwnProperty("WIFE") ? getFirstValue(nextFamily["WIFE"]) : null;
       var fatherLink = nextFamily.hasOwnProperty("HUSB") ? getFirstValue(nextFamily["HUSB"]) : null;
                  
       if (fatherLink == null) {
           var fatherID = newG._addVertex( null, TYPE.PERSON, {"gender": "M", "comments": "unknown"}, newG.defaultPersonNodeWidth );
       } else {
           var fatherID = externalIDToID[fatherLink];
           if (newG.properties[fatherID].gender == "F")
               throw "Unable to import pedigree: a person declared as female is also declared as being a father ("+fatherLink+")";
       }
       if (motherLink == null) {
           var motherID = newG._addVertex( null, TYPE.PERSON, {"gender": "F", "comments": "unknown"}, newG.defaultPersonNodeWidth );
       } else {
           var motherID = externalIDToID[motherLink];
           if (newG.properties[motherID].gender == "M")
               throw "Unable to import pedigree: a person declared as male is also declared as being a mother ("+motherLink+")";          
       }

       var chhubID = relationshipTracker.createOrGetChildhub(motherID, fatherID);

       var children = nextFamily.hasOwnProperty("CHIL") ? nextFamily["CHIL"] : null;
       
       if (children == null) {
           var childID = newG._addVertex( null, TYPE.PERSON, {"gender": "U", "comments": "unknown"}, newG.defaultPersonNodeWidth );
           noChildFamilies.push(nextFamily.id);
           externalIDToID[childID] = childID;
           children = [{"value": childID}];
       }
       
       for (var j = 0; j < children.length; j++) {
           var externalID = children[j].value;
           
           var childID = externalIDToID.hasOwnProperty(externalID) ? externalIDToID[externalID] : null;
           
           if (childID == null) {
               throw "Unable to import pedigree: child link does not point to an existing individual: [" + externalID + "]";
           }
           
           newG.addEdge( chhubID, childID, defaultEdgeWeight );
       }
   }

   if (noChildFamilies.length > 0) {
       alert("Some families with no children were found in the imported pedigree: this is not supported at the moment, so a child was added to each childless family");
   }
   
   PedigreeImport.validateBaseGraph(newG);

   return newG;
}

PedigreeImport.JSONToInternalPropertyMapping = {
        "proband":         "proband", 
        "lastname":        "lName",
        "lastnameatbirth": "lNameAtB",
        "comments":        "comments",
        "twingroup":       "twinGroup",
        "monozygotic":     "monozygotic",
        "unknownzygotic":  "unknownzygotic",
        "adoptedin":       "isAdopted",
        "evaluated":       "evaluated",
        "evaluatedinfo":   "evaluatedinfo",
        "birthdate":       "dob",
        "deathdate":       "dod",
        "gestationage":    "gestationAge",
        "lifestatus":      "lifeStatus",
        "disorders":       "disorders",
        "cancer":           "cancer",
        // "canceragenum":    "cancerAgeNum",
        // "canceragetype":   "cancerAgeType",
        "cancertypes":    					"cancerTypes",
        "raretumorname":    					"rareTumorName",
        "othertumorname":    				"otherTumorName",
        "gastrointestinaltumorname":    		"gastrointestinalTumorName",
        "urogenitaltumorname":    			"urogenitalTumorName",
        "highrelativecancername":    		"highRelativeCancerName",
        "hpoterms":        "hpoTerms",
        "candidategenes":  "candidateGenes",
        "ethnicities":     "ethnicities",
        "carrierstatus":   "carrierStatus",
        "externalid":      "externalID",
        "numpersons":      "numPersons",
        "lostcontact":     "lostContact",
        "nodenumber":      "nodeNumber",
        
        "sex":        		  "gender",
        "firstname":        "fName",
        "probability":    	"probability",
        "curentage":    		"curentAge",
        "telnum":    		"telNum",
        "dateofadd":    		"dateOfAdd",
        "memberid":         "memberID",
        "bornaddr":         "bornAddr",
        "ethnicityname":    "ethnicityName",
        "deathage":         "deathAge",
        "deceasedtext":     "deceasedText",
        "childlessstatus": "childlessStatus",
        "childlessreason": "childlessReason",
        "memberid":"memberID",
        "precursor":"precursor",
        "familyUserId":"familyUserId",
        "id":"id"
    };


PedigreeImport.convertProperty = function(externalPropertyName, value) {
    if (!PedigreeImport.JSONToInternalPropertyMapping.hasOwnProperty(externalPropertyName))
        return null;
            
    var internalPropertyName = PedigreeImport.JSONToInternalPropertyMapping[externalPropertyName];
    return {"propertyName": internalPropertyName, "value": value };
}
PedigreeImport.JSONToInternalRelationshipPropertyMapping = {
            "childlessstatus": "childlessStatus",
            "childlessreason": "childlessReason",
            "consanguinity":   "consangr",
            "separated":       "broken"
};
PedigreeImport.convertRelationshipProperty = function(externalPropertyName, value) {
        try {
            if (!PedigreeImport.JSONToInternalRelationshipPropertyMapping.hasOwnProperty(externalPropertyName)) {
                return null;
            }

            var internalPropertyName = PedigreeImport.JSONToInternalRelationshipPropertyMapping[externalPropertyName];

            if (externalPropertyName == "consanguinity") {
                if (value != "Y" && value != "N"&& value != "E") {
                    return null;
                }
            }
            return {"propertyName": internalPropertyName, "value": value };
        } catch (err) {
            console.log("关系数据有误 [" + externalPropertyName + "]");
            return null;
        }
}

RelationshipTracker = function (newG, defaultEdgeWeight) {
    this.newG = newG;
    
    this.defaultEdgeWeight = defaultEdgeWeight;
    
    this.relationships = {};
    this.relChildHubs  = {};
    this.chhubRels     = {};
};

RelationshipTracker.prototype = {
        
    createOrGetChildhub: function (motherID, fatherID)
    {
        if (this.relationships.hasOwnProperty(motherID) && this.relationships[motherID].hasOwnProperty(fatherID)) {
            var relID   = this.relationships[motherID][fatherID];
            var chhubID = this.relChildHubs[relID];
        } else {
            if (this.relationships[motherID] === undefined) this.relationships[motherID] = {};
            if (this.relationships[fatherID] === undefined) this.relationships[fatherID] = {};
        
            var relID   = this.newG._addVertex( null, TYPE.RELATIONSHIP, {}, this.newG.defaultNonPersonNodeWidth );
            var chhubID = this.newG._addVertex( null, TYPE.CHILDHUB,     {}, this.newG.defaultNonPersonNodeWidth );
        
            this.newG.addEdge( relID,    chhubID, this.defaultEdgeWeight );
            this.newG.addEdge( motherID, relID,   this.defaultEdgeWeight );
            this.newG.addEdge( fatherID, relID,   this.defaultEdgeWeight );
        
            this.relationships[motherID][fatherID] = relID;
            this.relationships[fatherID][motherID] = relID;
            this.relChildHubs[relID] = chhubID;
            this.chhubRels[chhubID]  = relID;
        }
        
        return chhubID;
    },
	getRelationshipIDForChildHub: function(chhubID) {
        return this.chhubRels[chhubID];
    }
};
